﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using System.Threading;
using TankController.Dominio;

namespace TankController
{
    public partial class Grafico : UserControl
    {
        private const int PERIODO = 10;
        private const int MAX_TESTE_RUIDO_TEMPO_ACOMODACAO = 10;
        private const int MAX_TESTE_RUIDO_TEMPO_PICO = 10;

        protected FormPrincipal formPrincipal;

        private delegate void ResetAutoValuesCallback();
        private delegate void InvalidateChartCallback();
        private delegate void ClearPointsCallback(Series serie);
        private delegate void AddXYCallback(Series linhaAtual, double valor);
        private delegate void SetAnnotationTextCallback(String nome, String texto);

        private IList<ISinal> sinais;
        private IDictionary<ISinal, IDictionary<Color, Series>> series = new Dictionary<ISinal, IDictionary<Color, Series>>();

        private IDictionary<TipoSinal, IList<Color>> coresTipo = new Dictionary<TipoSinal, IList<Color>>();

        private Thread threadGrafico;
        //private int unidadeTempo;

        protected TimeSpan unidadeTempo
        {
            get { return DateTime.Now - Global.TempoInicio; }
        }

        private TimeSpan ultimaMudancaReferencia;
        //private double referenciaAnterior;
        private double? valorRespostaAnterior;
        private int? sinalDerivadaAnterior;
        private Boolean cruzouReferencia;
        private TimeSpan? tempoSubida;
        private double? sobreSinalMaximo;
        private TimeSpan? tempoPico;
        private TimeSpan? tempoAcomodacao2;
        private int contTesteRuidoTempoSubida;
        private int contTesteRuidoTempoPico;
        private int contTesteRuidoTempoAcomodacao;

        public Grafico(FormPrincipal formPrincipal, String titulo)
        {
            InitializeComponent();

            this.formPrincipal = formPrincipal;
            this.chart.Titles[0].Text = titulo;
        }

        internal void Preparar(IList<ISinal> sinais)
        {
            this.sinais = sinais;

            MontarSeries();
        }

        internal void Iniciar()
        {
            threadGrafico = new Thread(new ThreadStart(Executar));

            threadGrafico.Start();
        }

        private void MontarSeries()
        {
            flpSeries.Controls.Clear();
            series.Clear();
            chart.Series.Clear();
            this.coresTipo.Clear();

            this.coresTipo.Add(TipoSinal.Referencia, new Color[] { Color.Violet, Color.Goldenrod }.ToList());
            this.coresTipo.Add(TipoSinal.Nivel, new Color[] { Color.Blue, SystemColors.MenuHighlight }.ToList());
            this.coresTipo.Add(TipoSinal.Resposta, new Color[] { Color.Green, Color.Lavender }.ToList());
            this.coresTipo.Add(TipoSinal.Erro, new Color[] { Color.Red, Color.Orange }.ToList());
            this.coresTipo.Add(TipoSinal.Saida, new Color[] { Color.SeaGreen, Color.RoyalBlue }.ToList());

            Serie serie = null;

            foreach (ISinal sinal in sinais)
            {
                serie = new Serie(this, sinal, coresTipo[sinal.Tipo].First());

                coresTipo[sinal.Tipo].RemoveAt(0);

                series.Add(sinal, new Dictionary<Color, Series>());

                serie.Marcar();

                flpSeries.Controls.Add(serie);
            }
        }

        internal void AdicionarSinal(Serie serie, ISinal sinal, Color cor)
        {
            Series linha = chart.Series.Add(sinal.Descricao);

            linha.Color = cor;
            linha.ChartType = SeriesChartType.Line;

            series[sinal].Add(cor, linha);

            //DesabilitarSeries(serie, cor);
        }

        internal void RemoverSinal(ISinal sinal, Color cor)
        {
            chart.Series.Remove(series[sinal][cor]);

            series[sinal].Remove(cor);

            //HabilitarSeries(cor);
        }

        /*private void HabilitarSeries(Color cor)
        {
            foreach (Serie serie in flpSeries.Controls.OfType<Serie>())
            {
                serie.HabilitarCor(cor);
            }
        }

        private void DesabilitarSeries(Serie serie, Color cor)
        {
            foreach (Serie outraSerie in flpSeries.Controls.OfType<Serie>())
            {
                if (outraSerie != serie)
                {
                    outraSerie.DesbilitarCor(cor);
                }
            }
        }*/

        private void Executar()
        {
            Series linhaAtual = null;

            LimparSeries();

            AtualizarReferencia();

            valorRespostaAnterior = null;
            sinalDerivadaAnterior = null;

            while (Global.Status == Status.Play)
            {
                //lock (Global.MonitorSinais)
                //{
                    foreach (ISinal sinal in series.Keys)
                    {
                        foreach (Color cor in series[sinal].Keys)
                        {
                            linhaAtual = series[sinal][cor];

                            if (sinal.HasProximoPonto())
                            {
                                double[] valores = sinal.ConsumirProximoPonto();

                                /*if (sinal.Descricao == "Entrada")
                                {
                                    String a = "";
                                    Console.Out.WriteLine("Valores: " + valores.Aggregate(a, (b, v) => b.ToString() + v.ToString() + ", ").ToString() + " inseridos no tempo: " + unidadeTempo + ". Valor de tempo do SINAL: " + (sinal as Sinal).VerUnidadeTempoAtual);
                                }*/

                                // Análise se aplica apenas ao sinal de resposta de controladores em malha fechada
                                if (Global.TipoConfiguracao != TipoConfiguracao.ConfiguracaoA && sinal.Tipo == TipoSinal.Resposta)
                                {
                                    if (IsAnaliseIncompleta())
                                    {
                                        CalcularAnaliseResposta(valores[valores.Length - 1]);
                                    }
                                }

                                foreach (double valor in valores)
                                {
                                    //AddXY(linhaAtual, unidadeTempo, valor);
                                    AddXY(linhaAtual, valor);
                                }
                            }
                        }
                    }
                //}

                // Adjust Y & X axis scale
                ResetAutoValues();

                // Invalidate chart
                InvalidateChart();

                //unidadeTempo++;

                Thread.Sleep(PERIODO);
            }

            //unidadeTempo = 0;

            //ZerarAnaliseResposta();
        }

        private Boolean IsAnaliseIncompleta()
        {
            return tempoSubida == null ||
                tempoPico == null ||
                sobreSinalMaximo == null ||
                tempoAcomodacao2 == null;
        }

        internal void AtualizarReferencia()
        {
            ZerarAnaliseResposta();

            ultimaMudancaReferencia = DateTime.Now - Global.TempoInicio;
        }

        private void CalcularAnaliseResposta(double valorResposta)
        {
            double referencia = Global.Entrada.GetValorAmplitude((int)unidadeTempo.TotalMilliseconds);
            int periodo = (Global.Controlador1 as AbstractControlador).Periodo;
            double derivada = valorRespostaAnterior != null ? Math.Round((valorResposta - valorRespostaAnterior.Value) / periodo, 3) : 0;
            int sinalDerivada = valorRespostaAnterior != null ? Math.Sign(Math.Round(valorResposta - valorRespostaAnterior.Value, 3)) : 0;
            Boolean passouTesteRuidoTempoSubida = contTesteRuidoTempoSubida == MAX_TESTE_RUIDO_TEMPO_ACOMODACAO;
            Boolean passouTesteRuidoTempoPico = contTesteRuidoTempoPico == MAX_TESTE_RUIDO_TEMPO_PICO;
            Boolean passouTesteRuidoTempoAcomodacao = contTesteRuidoTempoAcomodacao == MAX_TESTE_RUIDO_TEMPO_ACOMODACAO;

            // Tempo de subida
            if (tempoSubida == null && 
                ((valorRespostaAnterior != null && valorRespostaAnterior < referencia && valorResposta >= referencia) ||
                valorRespostaAnterior != null && valorRespostaAnterior > referencia && valorResposta <= referencia))
            {
                //if (passouTesteRuidoTempoSubida)
                //{
                    tempoSubida = unidadeTempo - ultimaMudancaReferencia;

                    cruzouReferencia = true;
                //}
                //else
                //{
                //    contTesteRuidoTempoSubida++;
                //}
            }
            else
            {
                contTesteRuidoTempoSubida = 0;
            }

            //if (derivada < 0.1)
            //{
                // Tempo de pico e máximo sobressinal
                if (tempoPico == null && cruzouReferencia && (sinalDerivadaAnterior != null && (sinalDerivada == 0 || sinalDerivada != sinalDerivadaAnterior)))
                {
                    //if (passouTesteRuidoTempoPico)
                    //{
                        tempoPico = unidadeTempo - ultimaMudancaReferencia;

                        sobreSinalMaximo = Math.Abs(Math.Round(((valorResposta - referencia) / referencia) * 100, 2));
                        //sobreSinalMaximo = valorResposta;
                    //}
                }
                else
                {
                    contTesteRuidoTempoPico = 0;
                }
            //}
            //else
            //{
            //    contTesteRuidoTempoPico++;
            //}

            // Tempo de acomodação de 2%
            if (tempoAcomodacao2 == null && derivada == 0 && (valorResposta >= referencia * 0.98 && valorResposta <= referencia * 1.02))
            {
                if (passouTesteRuidoTempoAcomodacao)
                {
                    tempoAcomodacao2 = unidadeTempo - ultimaMudancaReferencia;
                }
                else
                {
                    contTesteRuidoTempoAcomodacao++;
                }
            }
            else
            {
                contTesteRuidoTempoAcomodacao = 0;
            }

            valorRespostaAnterior = valorResposta;
            //referenciaAnterior = referencia;
            sinalDerivadaAnterior = sinalDerivada;

            ExibirAnaliseResposta();
        }

        private void ZerarAnaliseResposta()
        {
            tempoSubida = null;
            tempoPico = null;
            sobreSinalMaximo = null;
            tempoAcomodacao2 = null;

            cruzouReferencia = false;

            ExibirAnaliseResposta();
        }

        private void ExibirAnaliseResposta()
        {
            if (tempoSubida != null)
            {
                SetAnnotationText("TempoSubida", tempoSubida.Value.ToString("mm'min 'ss's 'fff'ms'"));
            }
            else
            {
                SetAnnotationText("TempoSubida", "Indefinido");
            }

            if (tempoPico != null)
            {
                SetAnnotationText("TempoPico", tempoPico.Value.ToString("mm'min 'ss's 'fff'ms'"));
            }
            else
            {
                SetAnnotationText("TempoPico", "Indefinido");
            }

            if (sobreSinalMaximo != null)
            {
                SetAnnotationText("SobreSinalMaximo", sobreSinalMaximo.Value.ToString() + "%");
            }
            else
            {
                SetAnnotationText("SobreSinalMaximo", "Indefinido");
            }

            if (tempoAcomodacao2 != null)
            {
                SetAnnotationText("TempoAcomodacao2", tempoAcomodacao2.Value.ToString("mm'min 'ss's 'fff'ms'"));
            }
            else
            {
                SetAnnotationText("TempoAcomodacao2", "Indefinido");
            }
        }

        private void LimparSeries()
        {
            foreach (Series serie in chart.Series)
            {
                ClearPoints(serie);
            }
        }

        private void ResetAutoValues()
        {
            if (chart.InvokeRequired)
            {
                ResetAutoValuesCallback callback = new ResetAutoValuesCallback(ResetAutoValues);

                chart.Invoke(callback, null);
            }
            else
            {
                chart.ResetAutoValues();
            }
        }

        private void InvalidateChart()
        {
            if (chart.InvokeRequired)
            {
                InvalidateChartCallback callback = new InvalidateChartCallback(InvalidateChart);

                chart.Invoke(callback, null);
            }
            else
            {
                chart.Invalidate();
            }
        }

        private void ClearPoints(Series serie)
        {
            if (chart.InvokeRequired)
            {
                ClearPointsCallback callback = new ClearPointsCallback(ClearPoints);

                chart.Invoke(callback, new Object[] { serie });
            }
            else
            {
                serie.Points.Clear();
            }
        }

        private void AddXY(Series linhaAtual, double valor)
        {
            if (chart.InvokeRequired)
            {
                AddXYCallback callback = new AddXYCallback(AddXY);

                chart.Invoke(callback, new Object[] { linhaAtual, valor });
            }
            else
            {
                linhaAtual.Points.AddXY(unidadeTempo.TotalSeconds, valor);
            }
        }

        private void SetAnnotationText(String nome, String texto)
        {
            if (chart.InvokeRequired)
            {
                SetAnnotationTextCallback callback = new SetAnnotationTextCallback(SetAnnotationText);

                chart.Invoke(callback, new Object[] { nome, texto });
            }
            else
            {
                (chart.Annotations[nome] as TextAnnotation).Text = texto;
            }
        }
    }
}
